// zhangheng new
import BoundingSphere from '../Core/BoundingSphere.js';
import Cartesian2 from '../Core/Cartesian2.js';
import Cartesian3 from '../Core/Cartesian3.js';
import Check from '../Core/Check.js';
import clone from '../Core/clone.js';
import Color from '../Core/Color.js';
import ComponentDatatype from '../Core/ComponentDatatype.js';
import defaultValue from '../Core/defaultValue.js';
import defined from '../Core/defined.js';
import destroyObject from '../Core/destroyObject.js';
import DeveloperError from '../Core/DeveloperError.js';
import Matrix4 from '../Core/Matrix4.js';
import PrimitiveType from '../Core/PrimitiveType.js';
import Resource from '../Core/Resource.js';
import RuntimeError from '../Core/RuntimeError.js';
import Transforms from '../Core/Transforms.js';
import Buffer from '../Renderer/Buffer.js';
import BufferUsage from '../Renderer/BufferUsage.js';
import DrawCommand from '../Renderer/DrawCommand.js';
import Pass from '../Renderer/Pass.js';
import ShaderSource from '../Renderer/ShaderSource.js';
import ForEach from '../ThirdParty/GltfPipeline/ForEach.js';
import when from '../ThirdParty/when.js';
import Model from './Model.js';
// zhangheng update
// import ModelInstance from './ModelInstance.js';
import ModelInstancezh from './ModelInstancezh.js';
import ModelUtility from './ModelUtility.js';
import SceneMode from './SceneMode.js';
import ShadowMode from './ShadowMode.js';

var LoadState = {
    NEEDS_LOAD: 0,
    LOADING: 1,
    LOADED: 2,
    FAILED: 3
};

/**
 * A 3D model instance collection. All instances reference the same underlying model, but have unique
 * per-instance properties like model matrix, pick id, etc.
 *
 * Instances are rendered relative-to-center and for best results instances should be positioned close to one another.
 * Otherwise there may be precision issues if, for example, instances are placed on opposite sides of the globe.
 *
 * @alias ModelInstanceCollectionzh
 * @constructor
 *
 * @param {Object} options Object with the following properties:
 * @param {Object[]} [options.instances] An array of instances, where each instance contains a modelMatrix and optional batchId when options.batchTable is defined.
 * @param {Cesium3DTileBatchTable} [options.batchTable] The batch table of the instanced 3D Tile.
 * @param {Resource|String} [options.url] The url to the .gltf file.
 * @param {Object} [options.requestType] The request type, used for request prioritization
 * @param {Object|ArrayBuffer|Uint8Array} [options.gltf] A glTF JSON object, or a binary glTF buffer.
 * @param {Resource|String} [options.basePath=''] The base path that paths in the glTF JSON are relative to.
 * @param {Boolean} [options.dynamic=false] Hint if instance model matrices will be updated frequently.
 * @param {Boolean} [options.show=true] Determines if the collection will be shown.
 * @param {Boolean} [options.allowPicking=true] When <code>true</code>, each instance is pickable with {@link Scene#pick}.
 * @param {Boolean} [options.asynchronous=true] Determines if model WebGL resource creation will be spread out over several frames or block until completion once all glTF files are loaded.
 * @param {Boolean} [options.incrementallyLoadTextures=true] Determine if textures may continue to stream in after the model is loaded.
 * @param {ShadowMode} [options.shadows=ShadowMode.ENABLED] Determines whether the collection casts or receives shadows from light sources.
 * @param {Cartesian2} [options.imageBasedLightingFactor=new Cartesian2(1.0, 1.0)] Scales the diffuse and specular image-based lighting from the earth, sky, atmosphere and star skybox.
 * @param {Cartesian3} [options.lightColor] The light color when shading models. When <code>undefined</code> the scene's light color is used instead.
 * @param {Number} [options.luminanceAtZenith=0.2] The sun's luminance at the zenith in kilo candela per meter squared to use for this model's procedural environment map.
 * @param {Cartesian3[]} [options.sphericalHarmonicCoefficients] The third order spherical harmonic coefficients used for the diffuse color of image-based lighting.
 * @param {String} [options.specularEnvironmentMaps] A URL to a KTX file that contains a cube map of the specular lighting and the convoluted specular mipmaps.
 * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for the collection.
 * @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the instances in wireframe.
 *
 * @exception {DeveloperError} Must specify either <options.gltf> or <options.url>, but not both.
 * @exception {DeveloperError} Shader program cannot be optimized for instancing. Parameters cannot have any of the following semantics: MODEL, MODELINVERSE, MODELVIEWINVERSE, MODELVIEWPROJECTIONINVERSE, MODELINVERSETRANSPOSE.
 *
 * @private
 */
function ModelInstanceCollectionzh(options) {
    options = defaultValue(options, defaultValue.EMPTY_OBJECT);

    //>>includeStart('debug', pragmas.debug);
    if (!defined(options.gltf) && !defined(options.url)) {
        throw new DeveloperError('Either options.gltf or options.url is required.');
    }

    if (defined(options.gltf) && defined(options.url)) {
        throw new DeveloperError('Cannot pass in both options.gltf and options.url.');
    }
    //>>includeEnd('debug');

    this.show = defaultValue(options.show, true);

    this._instancingSupported = false;
    this._dynamic = defaultValue(options.dynamic, false);
    this._allowPicking = defaultValue(options.allowPicking, true);
    this._ready = false;
    this._readyPromise = when.defer();
    this._state = LoadState.NEEDS_LOAD;
    this._dirty = false;

    // Undocumented options
    this._cull = defaultValue(options.cull, true);
    this._opaquePass = defaultValue(options.opaquePass, Pass.OPAQUE);

    this._instances = createInstances(this, options.instances);

    // When the model instance collection is backed by an i3dm tile,
    // use its batch table resources to modify the shaders, attributes, and uniform maps.
    this._batchTable = options.batchTable;

    this._model = undefined;
    this._vertexBufferTypedArray = undefined; // Hold onto the vertex buffer contents when dynamic is true
    this._vertexBuffer = undefined;
    this._batchIdBuffer = undefined;
    this._instancedUniformsByProgram = undefined;

    this._drawCommands = [];
    this._modelCommands = undefined;

    this._boundingSphere = createBoundingSphere(this);
    this._center = Cartesian3.clone(this._boundingSphere.center);
    this._rtcTransform = new Matrix4();
    this._rtcModelView = new Matrix4(); // Holds onto uniform

    this._mode = undefined;

    this.modelMatrix = Matrix4.clone(Matrix4.IDENTITY);
    this._modelMatrix = Matrix4.clone(this.modelMatrix);

    // Passed on to Model
    this._url = Resource.createIfNeeded(options.url);
    this._requestType = options.requestType;
    this._gltf = options.gltf;
    this._basePath = Resource.createIfNeeded(options.basePath);
    this._asynchronous = options.asynchronous;
    this._incrementallyLoadTextures = options.incrementallyLoadTextures;
    this._upAxis = options.upAxis; // Undocumented option
    this._forwardAxis = options.forwardAxis; // Undocumented option

    this.shadows = defaultValue(options.shadows, ShadowMode.ENABLED);
    this._shadows = this.shadows;

    this._pickIdLoaded = options.pickIdLoaded;

    this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false);
    this._debugShowBoundingVolume = false;

    this.debugWireframe = defaultValue(options.debugWireframe, false);
    this._debugWireframe = false;

    this._imageBasedLightingFactor = new Cartesian2(1.0, 1.0);
    Cartesian2.clone(options.imageBasedLightingFactor, this._imageBasedLightingFactor);
    this.lightColor = options.lightColor;
    this.luminanceAtZenith = options.luminanceAtZenith;
    this.sphericalHarmonicCoefficients = options.sphericalHarmonicCoefficients;
    this.specularEnvironmentMaps = options.specularEnvironmentMaps;

    //zhangheng add
    this._color = defaultValue(options.color, Cesium.Color.WHITE)
    this._id = defaultValue(options.id, null)
}

Object.defineProperties(ModelInstanceCollectionzh.prototype, {
    allowPicking: {
        get: function() {
            return this._allowPicking;
        }
    },
    length: {
        get: function() {
            return this._instances.length;
        }
    },
    activeAnimations: {
        get: function() {
            return this._model.activeAnimations;
        }
    },
    ready: {
        get: function() {
            return this._ready;
        }
    },
    readyPromise: {
        get: function() {
            return this._readyPromise.promise;
        }
    },
    imageBasedLightingFactor: {
        get: function() {
            return this._imageBasedLightingFactor;
        },
        set: function(value) {
            //>>includeStart('debug', pragmas.debug);
            Check.typeOf.object('imageBasedLightingFactor', value);
            Check.typeOf.number.greaterThanOrEquals('imageBasedLightingFactor.x', value.x, 0.0);
            Check.typeOf.number.lessThanOrEquals('imageBasedLightingFactor.x', value.x, 1.0);
            Check.typeOf.number.greaterThanOrEquals('imageBasedLightingFactor.y', value.y, 0.0);
            Check.typeOf.number.lessThanOrEquals('imageBasedLightingFactor.y', value.y, 1.0);
            //>>includeEnd('debug');
            Cartesian2.clone(value, this._imageBasedLightingFactor);
        }
    }
});

function createInstances(collection, instancesOptions) {
    instancesOptions = defaultValue(instancesOptions, []);
    var length = instancesOptions.length;
    var instances = new Array(length);
    for (var i = 0; i < length; ++i) {
        var instanceOptions = instancesOptions[i];
        var modelMatrix = instanceOptions.modelMatrix;
        var instanceId = defaultValue(instanceOptions.batchId, i);
        var modelId = defaultValue(instanceOptions.modelId);
        instances[i] = new ModelInstancezh(collection, modelMatrix, instanceId, modelId);
    }
    return instances;
}

function createBoundingSphere(collection) {
    var instancesLength = collection.length;
    var points = new Array(instancesLength);
    for (var i = 0; i < instancesLength; ++i) {
        points[i] = Matrix4.getTranslation(collection._instances[i]._modelMatrix, new Cartesian3());
    }

    return BoundingSphere.fromPoints(points);
}

var scratchCartesian = new Cartesian3();
var scratchMatrix = new Matrix4();

ModelInstanceCollectionzh.prototype.expandBoundingSphere = function(instanceModelMatrix) {
    var translation = Matrix4.getTranslation(instanceModelMatrix, scratchCartesian);
    BoundingSphere.expand(this._boundingSphere, translation, this._boundingSphere);
};

function getCheckUniformSemanticFunction(modelSemantics, supportedSemantics, programId, uniformMap) {
    return function(uniform, uniformName) {
        var semantic = uniform.semantic;
        if (defined(semantic) && (modelSemantics.indexOf(semantic) > -1)) {
            if (supportedSemantics.indexOf(semantic) > -1) {
                uniformMap[uniformName] = semantic;
            } else {
                throw new RuntimeError('Shader program cannot be optimized for instancing. ' +
                    'Uniform "' + uniformName + '" in program "' + programId +
                    '" uses unsupported semantic "' + semantic + '"'
                );
            }
        }
    };
}

function getInstancedUniforms(collection, programId) {
    if (defined(collection._instancedUniformsByProgram)) {
        return collection._instancedUniformsByProgram[programId];
    }

    var instancedUniformsByProgram = {};
    collection._instancedUniformsByProgram = instancedUniformsByProgram;

    // When using CESIUM_RTC_MODELVIEW the CESIUM_RTC center is ignored. Instances are always rendered relative-to-center.
    var modelSemantics = ['MODEL', 'MODELVIEW', 'CESIUM_RTC_MODELVIEW', 'MODELVIEWPROJECTION', 'MODELINVERSE', 'MODELVIEWINVERSE', 'MODELVIEWPROJECTIONINVERSE', 'MODELINVERSETRANSPOSE', 'MODELVIEWINVERSETRANSPOSE'];
    var supportedSemantics = ['MODELVIEW', 'CESIUM_RTC_MODELVIEW', 'MODELVIEWPROJECTION', 'MODELVIEWINVERSETRANSPOSE'];

    var techniques = collection._model._sourceTechniques;
    for (var techniqueId in techniques) {
        if (techniques.hasOwnProperty(techniqueId)) {
            var technique = techniques[techniqueId];
            var program = technique.program;

            // Different techniques may share the same program, skip if already processed.
            // This assumes techniques that share a program do not declare different semantics for the same uniforms.
            if (!defined(instancedUniformsByProgram[program])) {
                var uniformMap = {};
                instancedUniformsByProgram[program] = uniformMap;
                ForEach.techniqueUniform(technique, getCheckUniformSemanticFunction(modelSemantics, supportedSemantics, programId, uniformMap));
            }
        }
    }

    return instancedUniformsByProgram[programId];
}

function getVertexShaderCallback(collection) {
    return function(vs, programId) {
        var instancedUniforms = getInstancedUniforms(collection, programId);
        var usesBatchTable = defined(collection._batchTable);

        var renamedSource = ShaderSource.replaceMain(vs, 'czm_instancing_main');

        var globalVarsHeader = '';
        var globalVarsMain = '';
        for (var uniform in instancedUniforms) {
            if (instancedUniforms.hasOwnProperty(uniform)) {
                var semantic = instancedUniforms[uniform];
                var varName;
                if (semantic === 'MODELVIEW' || semantic === 'CESIUM_RTC_MODELVIEW') {
                    varName = 'czm_instanced_modelView';
                } else if (semantic === 'MODELVIEWPROJECTION') {
                    varName = 'czm_instanced_modelViewProjection';
                    globalVarsHeader += 'mat4 czm_instanced_modelViewProjection;\n';
                    globalVarsMain += 'czm_instanced_modelViewProjection = czm_projection * czm_instanced_modelView;\n';
                } else if (semantic === 'MODELVIEWINVERSETRANSPOSE') {
                    varName = 'czm_instanced_modelViewInverseTranspose';
                    globalVarsHeader += 'mat3 czm_instanced_modelViewInverseTranspose;\n';
                    globalVarsMain += 'czm_instanced_modelViewInverseTranspose = mat3(czm_instanced_modelView);\n';
                }

                // Remove the uniform declaration
                var regex = new RegExp('uniform.*' + uniform + '.*');
                renamedSource = renamedSource.replace(regex, '');

                // Replace all occurrences of the uniform with the global variable
                regex = new RegExp(uniform + '\\b', 'g');
                renamedSource = renamedSource.replace(regex, varName);
            }
        }

        // czm_instanced_model is the model matrix of the instance relative to center
        // czm_instanced_modifiedModelView is the transform from the center to view
        // czm_instanced_nodeTransform is the local offset of the node within the model
        var uniforms =
            'uniform mat4 czm_instanced_modifiedModelView;\n' +
            'uniform mat4 czm_instanced_nodeTransform;\n';

        var batchIdAttribute;
        var pickAttribute;
        var pickVarying;

        if (usesBatchTable) {
            batchIdAttribute = 'attribute float a_batchId;\n';
            pickAttribute = '';
            pickVarying = '';
        } else {
            batchIdAttribute = '';
            pickAttribute = 'attribute vec4 pickColor;\n' +
                'varying vec4 v_pickColor;\n';
            pickVarying = '    v_pickColor = pickColor;\n';
        }

        var instancedSource =
            uniforms +
            globalVarsHeader +
            'mat4 czm_instanced_modelView;\n' +
            'attribute vec4 czm_modelMatrixRow0;\n' +
            'attribute vec4 czm_modelMatrixRow1;\n' +
            'attribute vec4 czm_modelMatrixRow2;\n' +
            batchIdAttribute +
            pickAttribute +
            renamedSource +
            'void main()\n' +
            '{\n' +
            '    mat4 czm_instanced_model = mat4(czm_modelMatrixRow0.x, czm_modelMatrixRow1.x, czm_modelMatrixRow2.x, 0.0, czm_modelMatrixRow0.y, czm_modelMatrixRow1.y, czm_modelMatrixRow2.y, 0.0, czm_modelMatrixRow0.z, czm_modelMatrixRow1.z, czm_modelMatrixRow2.z, 0.0, czm_modelMatrixRow0.w, czm_modelMatrixRow1.w, czm_modelMatrixRow2.w, 1.0);\n' +
            '    czm_instanced_modelView = czm_instanced_modifiedModelView * czm_instanced_model * czm_instanced_nodeTransform;\n' +
            globalVarsMain +
            '    czm_instancing_main();\n' +
            pickVarying +
            '}\n';

        if (usesBatchTable) {
            var gltf = collection._model.gltf;
            var diffuseAttributeOrUniformName = ModelUtility.getDiffuseAttributeOrUniform(gltf, programId);
            instancedSource = collection._batchTable.getVertexShaderCallback(true, 'a_batchId', diffuseAttributeOrUniformName)(instancedSource);
        }

        return instancedSource;
    };
}

function getFragmentShaderCallback(collection) {
    return function(fs, programId) {
        var batchTable = collection._batchTable;
        if (defined(batchTable)) {
            var gltf = collection._model.gltf;
            var diffuseAttributeOrUniformName = ModelUtility.getDiffuseAttributeOrUniform(gltf, programId);
            fs = batchTable.getFragmentShaderCallback(true, diffuseAttributeOrUniformName)(fs);
        } else {
            fs = 'varying vec4 v_pickColor;\n' + fs;
        }
        return fs;
    };
}

function createModifiedModelView(collection, context) {
    return function() {
        return Matrix4.multiply(context.uniformState.view, collection._rtcTransform, collection._rtcModelView);
    };
}

function createNodeTransformFunction(node) {
    return function() {
        return node.computedMatrix;
    };
}

function getUniformMapCallback(collection, context) {
    return function(uniformMap, programId, node) {
        uniformMap = clone(uniformMap);
        uniformMap.czm_instanced_modifiedModelView = createModifiedModelView(collection, context);
        uniformMap.czm_instanced_nodeTransform = createNodeTransformFunction(node);

        // Remove instanced uniforms from the uniform map
        var instancedUniforms = getInstancedUniforms(collection, programId);
        for (var uniform in instancedUniforms) {
            if (instancedUniforms.hasOwnProperty(uniform)) {
                delete uniformMap[uniform];
            }
        }

        if (defined(collection._batchTable)) {
            uniformMap = collection._batchTable.getUniformMapCallback()(uniformMap);
        }

        return uniformMap;
    };
}

function getVertexShaderNonInstancedCallback(collection) {
    return function(vs, programId) {
        if (defined(collection._batchTable)) {
            var gltf = collection._model.gltf;
            var diffuseAttributeOrUniformName = ModelUtility.getDiffuseAttributeOrUniform(gltf, programId);
            vs = collection._batchTable.getVertexShaderCallback(true, 'a_batchId', diffuseAttributeOrUniformName)(vs);
            // Treat a_batchId as a uniform rather than a vertex attribute
            vs = 'uniform float a_batchId\n;' + vs;
        }
        return vs;
    };
}

function getFragmentShaderNonInstancedCallback(collection) {
    return function(fs, programId) {
        var batchTable = collection._batchTable;
        if (defined(batchTable)) {
            var gltf = collection._model.gltf;
            var diffuseAttributeOrUniformName = ModelUtility.getDiffuseAttributeOrUniform(gltf, programId);
            fs = batchTable.getFragmentShaderCallback(true, diffuseAttributeOrUniformName)(fs);
        } else {
            fs = 'uniform vec4 czm_pickColor;\n' + fs;
        }
        return fs;
    };
}

function getUniformMapNonInstancedCallback(collection) {
    return function(uniformMap) {
        if (defined(collection._batchTable)) {
            uniformMap = collection._batchTable.getUniformMapCallback()(uniformMap);
        }

        return uniformMap;
    };
}

function getVertexBufferTypedArray(collection) {
    var instances = collection._instances;
    var instancesLength = collection.length;
    var collectionCenter = collection._center;
    var vertexSizeInFloats = 12;

    var bufferData = collection._vertexBufferTypedArray;
    if (!defined(bufferData)) {
        bufferData = new Float32Array(instancesLength * vertexSizeInFloats);
    }
    if (collection._dynamic) {
        // Hold onto the buffer data so we don't have to allocate new memory every frame.
        collection._vertexBufferTypedArray = bufferData;
    }

    for (var i = 0; i < instancesLength; ++i) {
        var modelMatrix = instances[i]._modelMatrix;

        // Instance matrix is relative to center
        var instanceMatrix = Matrix4.clone(modelMatrix, scratchMatrix);
        instanceMatrix[12] -= collectionCenter.x;
        instanceMatrix[13] -= collectionCenter.y;
        instanceMatrix[14] -= collectionCenter.z;

        var offset = i * vertexSizeInFloats;

        // First three rows of the model matrix
        bufferData[offset + 0] = instanceMatrix[0];
        bufferData[offset + 1] = instanceMatrix[4];
        bufferData[offset + 2] = instanceMatrix[8];
        bufferData[offset + 3] = instanceMatrix[12];
        bufferData[offset + 4] = instanceMatrix[1];
        bufferData[offset + 5] = instanceMatrix[5];
        bufferData[offset + 6] = instanceMatrix[9];
        bufferData[offset + 7] = instanceMatrix[13];
        bufferData[offset + 8] = instanceMatrix[2];
        bufferData[offset + 9] = instanceMatrix[6];
        bufferData[offset + 10] = instanceMatrix[10];
        bufferData[offset + 11] = instanceMatrix[14];
    }

    return bufferData;
}

function createVertexBuffer(collection, context) {
    var i;
    var instances = collection._instances;
    var instancesLength = collection.length;
    var dynamic = collection._dynamic;
    var usesBatchTable = defined(collection._batchTable);

    if (usesBatchTable) {
        var batchIdBufferData = new Uint16Array(instancesLength);
        for (i = 0; i < instancesLength; ++i) {
            batchIdBufferData[i] = instances[i]._instanceId;
        }
        collection._batchIdBuffer = Buffer.createVertexBuffer({
            context: context,
            typedArray: batchIdBufferData,
            usage: BufferUsage.STATIC_DRAW
        });
    }

    if (!usesBatchTable) {
        var pickIdBuffer = new Uint8Array(instancesLength * 4);
        for (i = 0; i < instancesLength; ++i) {
            var pickId = collection._pickIds[i];
            var pickColor = pickId.color;
            var offset = i * 4;
            pickIdBuffer[offset] = Color.floatToByte(pickColor.red);
            pickIdBuffer[offset + 1] = Color.floatToByte(pickColor.green);
            pickIdBuffer[offset + 2] = Color.floatToByte(pickColor.blue);
            pickIdBuffer[offset + 3] = Color.floatToByte(pickColor.alpha);
        }
        collection._pickIdBuffer = Buffer.createVertexBuffer({
            context: context,
            typedArray: pickIdBuffer,
            usage: BufferUsage.STATIC_DRAW
        });
    }

    var vertexBufferTypedArray = getVertexBufferTypedArray(collection);
    collection._vertexBuffer = Buffer.createVertexBuffer({
        context: context,
        typedArray: vertexBufferTypedArray,
        usage: dynamic ? BufferUsage.STREAM_DRAW : BufferUsage.STATIC_DRAW
    });
}

function updateVertexBuffer(collection) {
    var vertexBufferTypedArray = getVertexBufferTypedArray(collection);
    collection._vertexBuffer.copyFromArrayView(vertexBufferTypedArray);
}

function createPickIds(collection, context) {
    // PERFORMANCE_IDEA: we could skip the pick buffer completely by allocating
    // a continuous range of pickIds and then converting the base pickId + batchId
    // to RGBA in the shader.  The only consider is precision issues, which might
    // not be an issue in WebGL 2.
    var instances = collection._instances;
    var instancesLength = instances.length;
    var pickIds = new Array(instancesLength);
    for (var i = 0; i < instancesLength; ++i) {
        pickIds[i] = context.createPickId(instances[i]);
    }
    return pickIds;
}

function createModel(collection, context) {
    var instancingSupported = collection._instancingSupported;
    var usesBatchTable = defined(collection._batchTable);
    var allowPicking = collection._allowPicking;

    var modelOptions = {
        id: collection._id,
        url: collection._url,
        requestType: collection._requestType,
        gltf: collection._gltf,
        basePath: collection._basePath,
        shadows: collection._shadows,
        cacheKey: undefined,
        asynchronous: collection._asynchronous,
        allowPicking: allowPicking,
        incrementallyLoadTextures: collection._incrementallyLoadTextures,
        upAxis: collection._upAxis,
        forwardAxis: collection._forwardAxis,
        precreatedAttributes: undefined,
        vertexShaderLoaded: undefined,
        fragmentShaderLoaded: undefined,
        uniformMapLoaded: undefined,
        pickIdLoaded: collection._pickIdLoaded,
        ignoreCommands: true,
        opaquePass: collection._opaquePass,
        imageBasedLightingFactor: collection.imageBasedLightingFactor,
        lightColor: collection.lightColor,
        luminanceAtZenith: collection.luminanceAtZenith,
        sphericalHarmonicCoefficients: collection.sphericalHarmonicCoefficients,
        specularEnvironmentMaps: collection.specularEnvironmentMaps,
        //zhangheng add
        color: collection._color
    };

    if (!usesBatchTable) {
        collection._pickIds = createPickIds(collection, context);
    }

    if (instancingSupported) {
        createVertexBuffer(collection, context);

        var vertexSizeInFloats = 12;
        var componentSizeInBytes = ComponentDatatype.getSizeInBytes(ComponentDatatype.FLOAT);

        var instancedAttributes = {
            czm_modelMatrixRow0: {
                index: 0, // updated in Model
                vertexBuffer: collection._vertexBuffer,
                componentsPerAttribute: 4,
                componentDatatype: ComponentDatatype.FLOAT,
                normalize: false,
                offsetInBytes: 0,
                strideInBytes: componentSizeInBytes * vertexSizeInFloats,
                instanceDivisor: 1
            },
            czm_modelMatrixRow1: {
                index: 0, // updated in Model
                vertexBuffer: collection._vertexBuffer,
                componentsPerAttribute: 4,
                componentDatatype: ComponentDatatype.FLOAT,
                normalize: false,
                offsetInBytes: componentSizeInBytes * 4,
                strideInBytes: componentSizeInBytes * vertexSizeInFloats,
                instanceDivisor: 1
            },
            czm_modelMatrixRow2: {
                index: 0, // updated in Model
                vertexBuffer: collection._vertexBuffer,
                componentsPerAttribute: 4,
                componentDatatype: ComponentDatatype.FLOAT,
                normalize: false,
                offsetInBytes: componentSizeInBytes * 8,
                strideInBytes: componentSizeInBytes * vertexSizeInFloats,
                instanceDivisor: 1
            }
        };

        // When using a batch table, add a batch id attribute
        if (usesBatchTable) {
            instancedAttributes.a_batchId = {
                index: 0, // updated in Model
                vertexBuffer: collection._batchIdBuffer,
                componentsPerAttribute: 1,
                componentDatatype: ComponentDatatype.UNSIGNED_SHORT,
                normalize: false,
                offsetInBytes: 0,
                strideInBytes: 0,
                instanceDivisor: 1
            };
        }

        if (!usesBatchTable) {
            instancedAttributes.pickColor = {
                index: 0, // updated in Model
                vertexBuffer: collection._pickIdBuffer,
                componentsPerAttribute: 4,
                componentDatatype: ComponentDatatype.UNSIGNED_BYTE,
                normalize: true,
                offsetInBytes: 0,
                strideInBytes: 0,
                instanceDivisor: 1
            };
        }

        modelOptions.precreatedAttributes = instancedAttributes;
        modelOptions.vertexShaderLoaded = getVertexShaderCallback(collection);
        modelOptions.fragmentShaderLoaded = getFragmentShaderCallback(collection);
        modelOptions.uniformMapLoaded = getUniformMapCallback(collection, context);

        if (defined(collection._url)) {
            modelOptions.cacheKey = collection._url.getUrlComponent() + '#instanced';
        }
    } else {
        modelOptions.vertexShaderLoaded = getVertexShaderNonInstancedCallback(collection);
        modelOptions.fragmentShaderLoaded = getFragmentShaderNonInstancedCallback(collection);
        modelOptions.uniformMapLoaded = getUniformMapNonInstancedCallback(collection, context);
    }
    //zhangheng flag
    if (defined(collection._url)) {
        collection._model = Model.fromGltf(modelOptions);
    } else {
        collection._model = new Model(modelOptions);
    }
}

function updateWireframe(collection) {
    if (collection._debugWireframe !== collection.debugWireframe) {
        collection._debugWireframe = collection.debugWireframe;

        // This assumes the original primitive was TRIANGLES and that the triangles
        // are connected for the wireframe to look perfect.
        var primitiveType = collection.debugWireframe ? PrimitiveType.LINES : PrimitiveType.TRIANGLES;
        var commands = collection._drawCommands;
        var length = commands.length;
        for (var i = 0; i < length; ++i) {
            commands[i].primitiveType = primitiveType;
        }
    }
}
function updateShowBoundingVolume(collection) {
    if (collection.debugShowBoundingVolume !== collection._debugShowBoundingVolume) {
        collection._debugShowBoundingVolume = collection.debugShowBoundingVolume;

        var commands = collection._drawCommands;
        var length = commands.length;
        for (var i = 0; i < length; ++i) {
            commands[i].debugShowBoundingVolume = collection.debugShowBoundingVolume;
        }
    }
}

function createCommands(collection, drawCommands) {
    var commandsLength = drawCommands.length;
    var instancesLength = collection.length;
    var boundingSphere = collection._boundingSphere;
    var cull = collection._cull;

    for (var i = 0; i < commandsLength; ++i) {
        var drawCommand = DrawCommand.shallowClone(drawCommands[i]);
        drawCommand.instanceCount = instancesLength;
        drawCommand.boundingVolume = boundingSphere;
        drawCommand.cull = cull;
        if (defined(collection._batchTable)) {
            drawCommand.pickId = collection._batchTable.getPickId();
        } else {
            drawCommand.pickId = 'v_pickColor';
        }
        collection._drawCommands.push(drawCommand);
    }
}

function createBatchIdFunction(batchId) {
    return function() {
        return batchId;
    };
}

function createPickColorFunction(color) {
    return function() {
        return color;
    };
}

function createCommandsNonInstanced(collection, drawCommands) {
    // When instancing is disabled, create commands for every instance.
    var instances = collection._instances;
    var commandsLength = drawCommands.length;
    var instancesLength = collection.length;
    var batchTable = collection._batchTable;
    var usesBatchTable = defined(batchTable);
    var cull = collection._cull;

    for (var i = 0; i < commandsLength; ++i) {
        for (var j = 0; j < instancesLength; ++j) {
            var drawCommand = DrawCommand.shallowClone(drawCommands[i]);
            drawCommand.modelMatrix = new Matrix4(); // Updated in updateCommandsNonInstanced
            drawCommand.boundingVolume = new BoundingSphere(); // Updated in updateCommandsNonInstanced
            drawCommand.cull = cull;
            drawCommand.uniformMap = clone(drawCommand.uniformMap);
            if (usesBatchTable) {
                drawCommand.uniformMap.a_batchId = createBatchIdFunction(instances[j]._instanceId);
            } else {
                var pickId = collection._pickIds[j];
                drawCommand.uniformMap.czm_pickColor = createPickColorFunction(pickId.color);
            }
            collection._drawCommands.push(drawCommand);
        }
    }
}

function updateCommandsNonInstanced(collection) {
    var modelCommands = collection._modelCommands;
    var commandsLength = modelCommands.length;
    var instancesLength = collection.length;
    var collectionTransform = collection._rtcTransform;
    var collectionCenter = collection._center;

    for (var i = 0; i < commandsLength; ++i) {
        var modelCommand = modelCommands[i];
        for (var j = 0; j < instancesLength; ++j) {
            var commandIndex = i * instancesLength + j;
            var drawCommand = collection._drawCommands[commandIndex];
            var instanceMatrix = Matrix4.clone(collection._instances[j]._modelMatrix, scratchMatrix);
            instanceMatrix[12] -= collectionCenter.x;
            instanceMatrix[13] -= collectionCenter.y;
            instanceMatrix[14] -= collectionCenter.z;
            instanceMatrix = Matrix4.multiply(collectionTransform, instanceMatrix, scratchMatrix);
            var nodeMatrix = modelCommand.modelMatrix;
            var modelMatrix = drawCommand.modelMatrix;
            Matrix4.multiply(instanceMatrix, nodeMatrix, modelMatrix);

            var nodeBoundingSphere = modelCommand.boundingVolume;
            var boundingSphere = drawCommand.boundingVolume;
            BoundingSphere.transform(nodeBoundingSphere, instanceMatrix, boundingSphere);
        }
    }
}

function getModelCommands(model) {
    var nodeCommands = model._nodeCommands;
    var length = nodeCommands.length;

    var drawCommands = [];

    for (var i = 0; i < length; ++i) {
        var nc = nodeCommands[i];
        if (nc.show) {
            drawCommands.push(nc.command);
        }
    }

    return drawCommands;
}

function commandsDirty(model) {
    var nodeCommands = model._nodeCommands;
    var length = nodeCommands.length;

    for (var i = 0; i < length; i++) {
        var nc = nodeCommands[i];
        if (nc.command.dirty) {
            return true;
        }
    }
    return false;
}

function generateModelCommands(modelInstanceCollection, instancingSupported) {
    modelInstanceCollection._drawCommands = [];

    var modelCommands = getModelCommands(modelInstanceCollection._model);
    if (instancingSupported) {
        createCommands(modelInstanceCollection, modelCommands);
    } else {
        createCommandsNonInstanced(modelInstanceCollection, modelCommands);
        updateCommandsNonInstanced(modelInstanceCollection);
    }
}

function updateShadows(collection) {
    if (collection.shadows !== collection._shadows) {
        collection._shadows = collection.shadows;

        var castShadows = ShadowMode.castShadows(collection.shadows);
        var receiveShadows = ShadowMode.receiveShadows(collection.shadows);

        var drawCommands = collection._drawCommands;
        var length = drawCommands.length;
        for (var i = 0; i < length; ++i) {
            var drawCommand = drawCommands[i];
            drawCommand.castShadows = castShadows;
            drawCommand.receiveShadows = receiveShadows;
        }
    }
}

ModelInstanceCollectionzh.prototype.update = function(frameState) {
    if (frameState.mode === SceneMode.MORPHING) {
        return;
    }

    if (!this.show) {
        return;
    }

    if (this.length === 0) {
        return;
    }

    var context = frameState.context;

    if (this._state === LoadState.NEEDS_LOAD) {
        this._state = LoadState.LOADING;
        this._instancingSupported = context.instancedArrays;
        createModel(this, context);
        var that = this;
        this._model.readyPromise.otherwise(function(error) {
            that._state = LoadState.FAILED;
            that._readyPromise.reject(error);
        });
    }

    var instancingSupported = this._instancingSupported;
    var model = this._model;

    model.imageBasedLightingFactor = this.imageBasedLightingFactor;
    model.lightColor = this.lightColor;
    model.luminanceAtZenith = this.luminanceAtZenith;
    model.sphericalHarmonicCoefficients = this.sphericalHarmonicCoefficients;
    model.specularEnvironmentMaps = this.specularEnvironmentMaps;

    model.update(frameState);

    if (model.ready && (this._state === LoadState.LOADING)) {
        this._state = LoadState.LOADED;
        this._ready = true;

        // Expand bounding volume to fit the radius of the loaded model including the model's offset from the center
        var modelRadius = model.boundingSphere.radius + Cartesian3.magnitude(model.boundingSphere.center);
        this._boundingSphere.radius += modelRadius;
        this._modelCommands = getModelCommands(model);

        generateModelCommands(this, instancingSupported);

        this._readyPromise.resolve(this);
        return;
    }

    if (this._state !== LoadState.LOADED) {
        return;
    }

    var modeChanged = (frameState.mode !== this._mode);
    var modelMatrix = this.modelMatrix;
    var modelMatrixChanged = !Matrix4.equals(this._modelMatrix, modelMatrix);

    if (modeChanged || modelMatrixChanged) {
        this._mode = frameState.mode;
        Matrix4.clone(modelMatrix, this._modelMatrix);
        var rtcTransform = Matrix4.multiplyByTranslation(this._modelMatrix, this._center, this._rtcTransform);
        if (this._mode !== SceneMode.SCENE3D) {
            rtcTransform = Transforms.basisTo2D(frameState.mapProjection, rtcTransform, rtcTransform);
        }
        Matrix4.getTranslation(rtcTransform, this._boundingSphere.center);
    }

    if (instancingSupported && this._dirty) {
        // If at least one instance has moved assume the collection is now dynamic
        this._dynamic = true;
        this._dirty = false;

        // PERFORMANCE_IDEA: only update dirty sub-sections instead of the whole collection
        updateVertexBuffer(this);
    }

    // If the model was set to rebuild shaders during update, rebuild instanced commands.
    if (commandsDirty(model)) {
        generateModelCommands(this, instancingSupported);
    }

    // If any node changes due to an animation, update the commands. This could be inefficient if the model is
    // composed of many nodes and only one changes, however it is probably fine in the general use case.
    // Only applies when instancing is disabled. The instanced shader automatically handles node transformations.
    if (!instancingSupported && (model.dirty || this._dirty || modeChanged || modelMatrixChanged)) {
        updateCommandsNonInstanced(this);
    }

    updateShadows(this);
    updateWireframe(this);
    updateShowBoundingVolume(this);

    var passes = frameState.passes;
    if (!passes.render && !passes.pick) {
        return;
    }

    var commandList = frameState.commandList;
    var commands = this._drawCommands;
    var commandsLength = commands.length;

    for (var i = 0; i < commandsLength; ++i) {
        commandList.push(commands[i]);
    }
};

ModelInstanceCollectionzh.prototype.isDestroyed = function() {
    return false;
};

ModelInstanceCollectionzh.prototype.destroy = function() {
    this._model = this._model && this._model.destroy();

    var pickIds = this._pickIds;
    if (defined(pickIds)) {
        var length = pickIds.length;
        for (var i = 0; i < length; ++i) {
            pickIds[i].destroy();
        }
    }

    return destroyObject(this);
};
export default ModelInstanceCollectionzh;
